Skip to content

fix(agc): wait for the display buffer's GPU writers before the presentation lock - #7643

Draft
EagleBearerYS wants to merge 4 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-present-display-flush-before-lock
Draft

EagleBearerYS wants to merge 4 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-present-display-flush-before-lock

Conversation

@EagleBearerYS

@EagleBearerYS EagleBearerYS commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What

On main, a present through guest memory reads the display buffer while the presenter holds the device lock. Driver::Present takes its second hold (Presentation.cpp ~108), and PresentDisplayBuffer reads the buffer under it: through GpuColorTransfer.cpp ~127, or ReadDisplayBuffer and DisplayBuffer.cpp ~252. That read's flush hook takes the lock again, nested. Because the hold isn't the outermost one (Recorder.cpp ~3605), the hook waits for the frame's recorded GPU writers with the lock held. Every queue worker and guest thread that needs the lock then stops for the frame's remaining GPU time, on each present.

The fix is fable-void's 531e450 from his wip/new-gpu-path, with his authorship.

  • The presenter flushes the display buffer's GPU writes before it takes the lock, unless the buffer is presented from its resident image.
  • The hook then holds the lock only at the outermost level, and waits for the GPU without it.
  • The read under the lock still flushes, so a writer recorded in between is still waited for.

Ported onto main with two changes, both named in the commit:

  • graphicsContext() stands for his readyContext();
  • APS5_NO_EARLY_DISPLAY_FLUSH, with its ENVIRONMENT.md row, keeps the old order.

The test is new. His agc_driver_cpu_read_lock_waits needs his NewPathFixture and residency layer, and runs only on Windows, so agc_driver_present_lock_wait re-implements its presentation case on main.

  • What it does: it presents a linear 64×64 display buffer through a headless surface while a ≥ 300 ms compute dispatch on queue 0x20 writes it. Meanwhile it takes the device lock from the test thread.
  • What it checks:
    • the present has to wait for the writer;
    • the longest lock wait beside it has to stay under a quarter of the writer's time;
    • the pixels the present read, from its CPU frame dump, have to hold the writer's value, so the present stays ordered after its writer.
  • A device without VK_EXT_headless_surface skips.

Pending: a Windows run of the new test, and a title whose presents go through guest memory; none found so far. Resident Evil Village (PPSA01556) and 007 First Light (PPSA11386) at their menus, Astro's Playroom (PPSA01325) in gameplay, and, per thuanquang's report, Metal Gear Solid Master Collection (PPSA14363) and Persona 5 Royal (PPSA05110) present from the resident image (Persona 5 Royal sends at most one of about 1,300 presents through guest memory), where this PR's path doesn't run (see Tested). This stays a draft until then.

Tested

  • Linux, lavapipe (Mesa 25.2.8, LLVM 20.1.2), CI's configure flags, on a cloud worker, agc_driver_present_lock_wait:

    build present longest lock wait beside it result
    this branch 457–480 ms 0.004–0.56 ms passes; 20 of 20 with --repeat until-fail:20, at most 2.42 ms over 60 attempts
    the Presentation.cpp line reverted 462–476 ms 452–466 ms fails: "the device lock was held for 452.016613 ms while the present waited for the display buffer's GPU writer (507.629233 ms)"
    this branch with APS5_NO_EARLY_DISPLAY_FLUSH=1 462–477 ms 452–467 ms fails the same way

    The writer ran ~489–508 ms each time, and every present read its value.

  • The order check catches a misordered read. A temporary mutant removed the read's own flush (DisplayBuffer.cpp:252). With the switch set, the test fails: "the present read 0x00000000 from the display buffer before its GPU writer stored 0xd2000001".

  • CI's Linux mirror on main 218aa8f and on this branch:

    • main: 1,391 passed, 69 not run, 5 failed;
    • this branch: 1,392 passed, 69 not run, 5 failed.

    The 5 failures are the worker's known IPv6 tests. The only difference is the new test.

  • Windows, NVIDIA GeForce RTX 4090 (driver 617.42, Windows 11 Pro 26200, WinLibs GCC 15.2.0, Release), run by MaxLikesCode on this head merged into main ad502d7 (comment):

  • Windows, AMD Radeon RX 6700 XT (gfx1031, driver 32.0.21045.16010, Windows 11 26200, WinLibs GCC 15.2.0 r7, Release), run by thuanquang on fdc4da3 merged onto main e05ba6f (comment), per their report:

    • agc_driver_present_lock_wait skips: AMD's Windows driver has no VK_EXT_headless_surface either (its surface extensions are VK_KHR_surface and VK_KHR_win32_surface), so neither proprietary Windows driver can run the test;
    • of the 1,464 tests tools/select_tests.py selects for this change, all pass except agc_host_import_cut_lifetime, agc_driver_depth_plane_partial_store and agc_driver_sample_order_grid_reference, which fail the same way on unmodified main 4017310 on that GPU (3 of 3 runs in each tree), and public_headers, which goes over its 30 s timeout there; its audit and its 64 compile batches pass when run on their own. tools/check_skips.py --job windows accepts all 10 skips.
  • Linux, RADV (the AMD Ryzen 9 7950X3D's integrated GPU, RADV RAPHAEL_MENDOCINO, Mesa 26.2.4; CachyOS, Linux 7.2.10, GCC 16.2.1, Release), run by SleglJan on this PR's 870ad5d on main b44d012 with the open PRs 007 First Light needs (comment):

    • agc_driver_present_lock_wait passes 13 of 13 runs with this PR's order; in the 8 verbose ones, the longest device-lock wait beside a present is 0.001–0.025 ms;
    • with APS5_NO_EARLY_DISPLAY_FLUSH=1 it fails 5 of 5: the longest wait is 308.6–310.6 ms, behind a 347.2 ms display-buffer writer;
    • every read is 0xd2000001 in both arms. The machine's RTX 3080 (NVIDIA 615.78.08) has no VK_EXT_headless_surface on Linux, so the test can't run on it.
  • 007 First Light (PPSA11386), main menu, the same machine's RTX 3080, this PR's order, by SleglJan (same comment): the last [flip] line under APS5_PROFILE_DRAW=1 reads 2,518 presents from the resident image and 0 through guest memory, so this PR's path didn't run there, and no frame-time A/B was taken.

  • Astro's Playroom (PPSA01325), Linux, RTX 5080 (driver 615.78.08), run by fma-rc on 870ad5d (comment): frames in a 10 s gameplay window, two rounds with the order reversed in the second. This PR: 129 and 121. APS5_NO_EARLY_DISPLAY_FLUSH=1: 136 and 132. The GPU sat at P0 at a median 2812 MHz. These counts differ by 5 and 9 % on a path that the profiled round below shows doesn't run in Playroom's gameplay, so they read as run-to-run spread, not this PR.

  • Astro's Playroom (PPSA01325), one profiled round per arm on fdc4da3, by fma-rc (comment): the same RTX 5080, APS5_PROFILE_DRAW=1, the last report period before the 10 s trace window closed. The last [flip] line reads 3,521 presents from the resident image by default and 3,182 with APS5_NO_EARLY_DISPLAY_FLUSH=1, with 0 through guest memory in both (0 not pending, 0 unsuitable). So this PR's path doesn't run there either.

  • Metal Gear Solid Master Collection (PPSA14363) and Persona 5 Royal (PPSA05110), the same RX 6700 XT, by thuanquang (same comment), per their report: Metal Gear Solid Master Collection's [flip] lines read 0 presents through guest memory, and Persona 5 Royal sends at most one of about 1,300 that way, every other present coming from the resident image, so this PR's path doesn't run in either.

  • Resident Evil Village (PPSA01556), Windows 11 Home 26300, NVIDIA GeForce RTX 3070 Laptop GPU (driver 617.42), AMD Ryzen 9 5900HS: this PR's 870ad5d merged with 63d5414 (fix(agc): put a retired import's unchanged ranges back in the write watch #8926's fix) on main 1ff5c8a. Each run sits at the main menu for 16 min with no input, with APS5_LAZY_SHADER_PREPARE=1 APS5_NO_GPL=1 APS5_HOST_IMPORT_MIB=6400 APS5_TRACE_SUMMARY_EVERY=1; the other arm adds APS5_NO_EARLY_DISPLAY_FLUSH=1. Order A B A B.

    this PR APS5_NO_EARLY_DISPLAY_FLUSH=1
    frames per second, 5–15 min after the menu 0.346, 0.363 (both: 0.355) 0.342, 0.370 (both: 0.356)
    frames per second, 2–8 min after the menu 0.344, 0.373 0.363, 0.437
    peak private bytes 25.3, 26.3 GB 26.6, 26.5 GB
    • The GPU was in P0 for 97–99% of nvidia-smi's samples (every ~0.1 s); clocks weren't locked (690–1785 MHz).
    • Village's menu doesn't run this PR's path. In one profiled 10-min run per arm (APS5_PROFILE_DRAW=1), the last [flip] line reads 4,366 presents from the resident image and 19 through guest memory with this PR, and 5,007 and 4 with the switch. The guest-memory counts are already there at the first line, at 573 and 583 presents, so they're boot presents. For a resident present PrepareDisplayRead returns before its flush, so at the menu the arms differ only by its PresentableResident lookup. This pair can't show the lock wait this PR removes; it only checks Village's menu for a regression. The switch's 2–8 min lead comes from its second run: its own two runs differ by 20% there.
    • In each arm, 2 of the 3 runs, counting the profiled one, ended in an access violation on the presenter thread when the harness closed the game. A run of 63d5414 without this PR ended the same way.
  • Pending: a Windows run of the new test on a GPU whose driver has VK_EXT_headless_surface; a title whose presents go through guest memory, none found so far.

  • fdc4da3, a merge of main 938812d. The only conflict was docs/dev/ENVIRONMENT.md, where main added APS5_NO_EMPTY_INDIRECT_SKIP next to this PR's APS5_NO_EARLY_DISPLAY_FLUSH; both rows are kept, in order, and the PR's own diff is unchanged. On GitHub's runners (run), of agc_driver_present_lock_wait, agc_driver_bda_device, agc_driver_image_table_reuse and agc_driver_pixel_lds, 3 pass 10 runs in a row on Linux and agc_driver_pixel_lds skips; on Windows all 4 skip without a Vulkan device; none fail. The libc and libkernel suites pass on both (119 and 201 tests on Linux, 146 and 211 on Windows), run with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.

  • 6afa853, a merge of main 828ac0b (run). The merge has no textual conflict, but without one change the new test doesn't build on main: orezraey's feat(videoout): composite the overlay bus over the main output #10507 gave AgcDriverPresentBuffer_nid_postfix a const DisplayBuffer* plane parameter for the overlay plane. The test's two calls in PresentLockWait.cpp now pass nullptr for it, as main's other callers without a plane do, and the merge commit's message names it. This PR's other changed lines are the same.

    • ×10: agc_driver_present_lock_wait, agc_driver_bda_device, agc_driver_image_table_reuse and agc_driver_pixel_lds.
      • Linux: 4 run, 3 pass, 1 skip, 0 fail. agc_driver_present_lock_wait passed every run; the skip is agc_driver_pixel_lds.
      • Windows: 4 run, 0 pass, 4 skip (no Vulkan device), 0 fail.
      • The libc and libkernel suites run 199 and 308 tests on Linux and 226 and 330 on Windows, with 0 failed. The suites ran with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.

Checklist

  • Based on main 218aa8f, and merges cleanly into current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source (none added)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: lavapipe (Mesa 25.2.8, LLVM 20.1.2), Ubuntu 24.04; NVIDIA GeForce RTX 4090 (driver 617.42), Windows 11, by MaxLikesCode (the new test skips there without VK_EXT_headless_surface); RADV RAPHAEL_MENDOCINO (Mesa 26.2.4) and RTX 3080 (615.78.08), CachyOS, by SleglJan; RTX 5080 (615.78.08), Linux, by fma-rc; AMD Radeon RX 6700 XT (driver 32.0.21045.16010), Windows 11, by thuanquang (the new test skips there without VK_EXT_headless_surface); NVIDIA GeForce RTX 3070 Laptop GPU (driver 617.42), Windows 11, with Resident Evil Village
  • AI-assisted: yes

fable-void and others added 2 commits October 18, 2026 17:51
…tation lock

A presentation through guest memory read the display buffer inside the
presenter's hold of the device lock. The flush hook of that read takes
the lock again, nested, so its wait for the frame's recorded writers
could not release it: every queue worker and every guest thread that
needed the lock stopped for the frame's remaining GPU time on each
present.

The presenter now flushes the display buffer's GPU writes before it
takes the lock, unless the buffer is presented from its resident image.
The hook then holds the lock only at the outermost level and waits for
the GPU without it. The read under the lock still flushes, so a writer
recorded in between is still waited for. APS5_NO_EARLY_DISPLAY_FLUSH
keeps the wait under the lock.

Ported from fable-void's 531e450 onto main: main's graphicsContext()
stands for his readyContext(), and the switch is added for the port.
…waits for the display buffer's writer

agc_driver_present_lock_wait presents a linear display buffer through a
headless surface while a long compute dispatch on queue 0x20 writes it,
and takes the device lock from the test thread meanwhile. The present
has to wait for the writer, and the longest lock wait beside it has to
stay under a quarter of the writer's time. The pixels each present read,
taken from its CPU frame dump, have to hold the value the writer stored,
so the present stays ordered after its writer. A device without
VK_EXT_headless_surface skips.

Based on fable-void's 531e450, whose agc_driver_cpu_read_lock_waits
presentation case needs his NewPathFixture and runs only on Windows.
@github-actions github-actions Bot added docs agc libSceAgc* labels Oct 18, 2026
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 10678 docs/dev/ENVIRONMENT.md L268

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1395 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 1896 docs/dev/ENVIRONMENT.md
PR 2434 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4472 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 5021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5925 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6186 docs/dev/ENVIRONMENT.md
PR 6188 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6804 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6924 docs/dev/ENVIRONMENT.md
PR 7275 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7449 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 7457 docs/dev/ENVIRONMENT.md
PR 7464 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7514 docs/dev/ENVIRONMENT.md
PR 7604 docs/dev/ENVIRONMENT.md
PR 8004 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 8246 docs/dev/ENVIRONMENT.md
PR 8484 docs/dev/ENVIRONMENT.md
PR 9012 docs/dev/ENVIRONMENT.md
PR 9345 docs/dev/ENVIRONMENT.md
PR 9356 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 9387 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 9712 docs/dev/ENVIRONMENT.md
PR 9852 docs/dev/ENVIRONMENT.md
PR 9898 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 10193 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 10442 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 10757 docs/dev/ENVIRONMENT.md
PR 10867 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 10894 docs/dev/ENVIRONMENT.md
PR 11351 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 11435 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 11436 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 11483 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 12120 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 12323 docs/dev/ENVIRONMENT.md
PR 12324 docs/dev/ENVIRONMENT.md
PR 12544 docs/dev/ENVIRONMENT.md
PR 12567 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 12746 docs/dev/ENVIRONMENT.md
PR 12828 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 12863 docs/dev/ENVIRONMENT.md
PR 12930 docs/dev/ENVIRONMENT.md
PR 12944 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 12970 docs/dev/ENVIRONMENT.md
PR 13148 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 13646 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 13707 docs/dev/ENVIRONMENT.md
PR 13863 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 13920 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 13928 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 13929 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 14217 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 14218 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 14262 docs/dev/ENVIRONMENT.md
PR 14264 docs/dev/ENVIRONMENT.md
PR 14299 docs/dev/ENVIRONMENT.md
PR 14344 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 14943 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 15059 docs/dev/ENVIRONMENT.md

@MaxLikesCode

MaxLikesCode commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

A Windows run, on NVIDIA: GeForce RTX 4090, driver 617.42, Windows 11 Pro 26200, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON with every execution test built. This head (870ad5d) merged into main ad502d7.

So a Windows run of the new test still needs a GPU whose driver has VK_EXT_headless_surface.

AI-assisted: yes (Claude Code)

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@MaxLikesCode thank you for the Windows run and for checking the three failures against main. The skip makes sense: the test needs VK_EXT_headless_surface for its presentation window, and NVIDIA's Windows driver doesn't expose it. I've added your run to the Tested section, and a Windows run of the test itself still needs a driver that has the extension.

@SleglJan

Copy link
Copy Markdown
Contributor

@EagleBearerYS a Linux run on a machine with an NVIDIA card. The new test doesn't reach the RTX 3080 here, so its numbers below are from RADV on the CPU's integrated GPU, where it separates the two orders. In 007 First Light's menu, on the RTX 3080, every present came from the resident image, where the early flush doesn't apply, so I didn't take a frame-time A/B there.

Setup:

agc_driver_present_lock_wait:

  • NVIDIA's Linux driver doesn't expose VK_EXT_headless_surface (vulkaninfo with only its ICD), but RADV does.
  • The skip check reads the instance's extensions, which the loader merges across drivers, so the test runs. Its first device selects the RTX 3080, and its windowed device then selects the integrated GPU. The test's log says Selected GPU name=AMD Ryzen 9 7950X3D 16-Core Processor (RADV RAPHAEL_MENDOCINO). The numbers below are that GPU's.
  • With VK_ICD_FILENAMES set to NVIDIA's ICD alone, the test skips: "the device has no VK_EXT_headless_surface for a presentation window".
  • This PR's order passes 13 of 13 runs, all with the same two-device selection. In the 8 verbose ones (24 attempts), each present takes 317.6–320.2 ms, and the longest device-lock wait beside it is 0.001–0.025 ms.
  • With APS5_NO_EARLY_DISPLAY_FLUSH=1 it fails 5 of 5 runs (15 attempts). Each present takes 318.9–320.7 ms, and the longest device-lock wait beside it is 308.6–310.6 ms, behind a 347.2 ms display-buffer writer.
  • Every read is 0xd2000001 in both arms.

007 First Light, main menu:

  • The run used this PR's order on the RTX 3080, in a 1500×844 XWayland window.
  • Switches: APS5_PROFILE_DRAW=1 and APS5_TRACE_SECONDS=1320:60, a 60 s trace window in the menu covering presents 2,153–2,317.
  • The title's executable is not patched. Pad input is scripted through APS5_PAD_INPUT_FILE; keyboard and mouse (APS5_NO_PAD_INPUT=1) and the physical controller are ignored.
  • The cumulative [flip] counter is printed every 10 s under the lock in PresentDisplayBuffer. At its last line, near the end of the run, it reads 2,518 presents from the resident image and 0 through guest memory. Its lines either side of the trace window read 2,127 and 2,322 resident, with 0 through guest memory both times.
  • PrepareDisplayRead makes the same PresentableResident lookup just before, outside the lock, and returns when it finds the image. With these counts it returns before FlushGpuWrites, though I didn't count that return directly.
  • An A/B of this PR there would differ only by that lookup, so I left it out.
  • For context, per present in the window (164 presents, frame p50 374.9 ms):
    • the presenter held the device lock 2.0 times, for 0.5 ms in total, with the longest single hold at 1.5 ms;
    • other threads waited 0.5 ms behind the present, against 131.8 ms behind queue dispatches.

Clocks on the RTX 3080 were not pinned. I measured them with nvidia-smi at 1 s intervals.

  • In the menu's trace window the GPU was in P2 in all 60 samples, at 2040 MHz graphics and 9251 MHz memory (nvidia-smi's maximums are 2220 and 9501 MHz). Power was 154 W at the median.
  • Over the whole run it was in P8 for 613 of the first 639 samples, while booting and loading; the other 26 were P2, P3 or P5, 24 of them in the first 41 s. After that it was in P2 for all 839 samples, the last 14 minutes, from before the title screen to the end.

AI-assisted: yes (Claude Code)

@fma-rc

fma-rc commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

@EagleBearerYS here's Astro's Playroom (PPSA01325) on this head (870ad5d), Linux, RTX 5080 (driver 615.78.08). Each run goes into the first level with Cross pressed and takes a 10 s trace window in the same gameplay scene. Two rounds, with the order reversed in the second.

Frames in the window round 1 round 2
this PR 129 121
APS5_NO_EARLY_DISPLAY_FLUSH=1 136 132

The switch was ahead in both rounds, by 5% and 9%. The GPU sat at P0 with a median graphics clock of 2812 MHz in all four runs, and the captures show the same intro, menu and gameplay with nothing missing. I didn't check which path Playroom's presents take on this card, so I can't say whether the early flush itself ran here.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@SleglJan Thanks, that's the red/green GitHub's Windows runner can't give. On RADV the test passes 13 of 13 with this PR's order. With APS5_NO_EARLY_DISPLAY_FLUSH=1 it fails 5 of 5: the longest device-lock wait beside a present is 308.6–310.6 ms there, behind the display-buffer writer, against 0.001–0.025 ms with the change. Thanks also for the 007 First Light (PPSA11386) counters. With every present coming from the resident image, the change only adds the PresentableResident lookup there, so leaving the A/B out was right.

@fma-rc Thanks for the two rounds in Astro's Playroom (PPSA01325). The switch being ahead in both is worth a closer look, since the change should only move the flush out of the presentation lock. If you can run one more round of each arm with APS5_PROFILE_DRAW=1, the [flip] lines it prints every 10 s would show whether Playroom's presents come from the resident image or through guest memory, and so whether the early flush runs there at all.

# Conflicts:
#	docs/dev/ENVIRONMENT.md
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@fma-rc following up on my ask from 10-21: could you run one more round of each arm in Astro's Playroom (PPSA01325) with APS5_PROFILE_DRAW=1, and paste the last [flip] line and the [lock] lines from your 10 s gameplay window, for each arm? That one switch prints both on this head, so nothing else is needed. On Resident Evil Village (PPSA01556)'s menu every present came from the resident image, so this PR's path didn't run there, and Astro's Playroom is so far the only title where the two arms differ.

@fma-rc

fma-rc commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

@EagleBearerYS one round per arm on head fdc4da3, Astro's Playroom on the RTX 5080, APS5_PROFILE_DRAW=1, the last report period before the 10 s trace window closed. In both arms every present came from the resident image (through guest memory: 0 not pending, 0 unsuitable), so this PR's path did not run here either.

APS5_NO_EARLY_DISPLAY_FLUSH=1:

[lock] queue 0x3d waited 21 ms for the GPU mutex in 2449 of 6878 acquisitions (10 s); by site (acquisitions/waits, waited): other 806/0 0 ms dispatch 1056/787 4 ms hook 528/69 1 ms label 3168/618 7 ms flush 1313/973 10 ms end 7/2 0 ms; held 264 ms in total, by site (holds, held, max): dispatch 1056 236 ms (max 4.6) label 3465 14 ms (max 0.1) flush 1313 8 ms (max 0.0) hook 528 4 ms (max 0.0) try 1682 1 ms (max 0.0) end 7 0 ms (max 0.0); waited behind (holder/site ms): untagged/draw 42; lock() 6878 / try 32331 / try failed 30352; locked GPU waits 344 / 174 ms
[lock] queue 0x0 waited 1 ms for the GPU mutex in 147 of 13843 acquisitions (10 s); by site (acquisitions/waits, waited): other 1868/0 0 ms dispatch 1862/3 0 ms indirect 532/0 0 ms draw 798/3 0 ms label 7182/10 1 ms flush 1468/3 0 ms fill 133/128 1 ms; held 966 ms in total, by site (holds, held, max): indirect 532 630 ms (max 4.9) dispatch 1862 251 ms (max 2.3) label 7182 44 ms (max 0.1) draw 798 28 ms (max 0.3) try 2745 6 ms (max 0.1) flush 1468 5 ms (max 0.0) fill 133 0 ms (max 0.0); waited behind (holder/site ms):; lock() 13843 / try 14047 / try failed 11302; locked GPU waits 469 / 431 ms
[lock] presenter waited 0 ms for the GPU mutex in 10 of 280 acquisitions (11 s); by site (acquisitions/waits, waited): present 280/10 0 ms; held 50 ms in total, by site (holds, held, max): present 280 50 ms (max 0.7); waited behind (holder/site ms):; lock() 280 / try 0 / try failed 0; locked GPU waits 0 / 0 ms
[lock] queue 0x52 waited 0 ms for the GPU mutex in 11 of 159 acquisitions (11 s); by site (acquisitions/waits, waited): copy 147/6 0 ms end 12/5 0 ms; held 3 ms in total, by site (holds, held, max): copy 147 2 ms (max 0.0) end 12 0 ms (max 0.0); waited behind (holder/site ms):; lock() 159 / try 0 / try failed 0; locked GPU waits 0 / 0 ms
[lock] untagged thread waited 279 ms for the GPU mutex in 3226 of 744565 acquisitions (10 s); by site (acquisitions/waits, waited): other 362806/0 0 ms draw 315077/2678 248 ms label 60768/518 29 ms flush 4106/22 1 ms fill 1548/6 0 ms end 260/2 0 ms; held 7628 ms in total, by site (holds, held, max): draw 315077 6253 ms (max 1.8) label 60768 1360 ms (max 17.7) fill 1548 14 ms (max 0.2) end 260 1 ms (max 0.0) flush 4106 0 ms (max 0.0); waited behind (holder/site ms): q0x3d/dispatch 238 presenter/present 45 q0x3d/flush 6 q0x3d/label 4; lock() 744565 / try 0 / try failed 0; locked GPU waits 128 / 1197 ms
[flip] (cumulative): 3182 presents from the resident image (0 refreshed first), through guest memory: 0 not pending, 0 unsuitable; 10 GPU frame dumps

Default:

[lock] presenter waited 0 ms for the GPU mutex in 4 of 255 acquisitions (10 s); by site (acquisitions/waits, waited): present 255/4 0 ms; held 45 ms in total, by site (holds, held, max): present 255 45 ms (max 0.7); waited behind (holder/site ms):; lock() 255 / try 0 / try failed 0; locked GPU waits 0 / 0 ms
[lock] queue 0x0 waited 1 ms for the GPU mutex in 140 of 12695 acquisitions (10 s); by site (acquisitions/waits, waited): other 1709/0 0 ms dispatch 1708/8 1 ms indirect 488/0 0 ms draw 732/1 0 ms label 6588/12 0 ms flush 1348/0 0 ms fill 122/119 0 ms; held 917 ms in total, by site (holds, held, max): indirect 488 588 ms (max 5.2) dispatch 1708 252 ms (max 2.7) label 6588 42 ms (max 0.1) draw 732 25 ms (max 0.1) try 2657 5 ms (max 0.0) flush 1349 5 ms (max 0.0) fill 122 0 ms (max 0.0); waited behind (holder/site ms):; lock() 12695 / try 13133 / try failed 10475; locked GPU waits 468 / 393 ms
[lock] queue 0x52 waited 0 ms for the GPU mutex in 20 of 163 acquisitions (12 s); by site (acquisitions/waits, waited): copy 141/10 0 ms end 22/10 0 ms; held 2 ms in total, by site (holds, held, max): copy 141 2 ms (max 0.0) end 22 0 ms (max 0.0); waited behind (holder/site ms):; lock() 163 / try 0 / try failed 0; locked GPU waits 0 / 0 ms
[lock] untagged thread waited 255 ms for the GPU mutex in 3553 of 698125 acquisitions (10 s); by site (acquisitions/waits, waited): other 336704/0 0 ms draw 298110/3020 228 ms label 57706/507 27 ms flush 3894/14 0 ms fill 1464/7 0 ms end 247/5 0 ms; held 7706 ms in total, by site (holds, held, max): draw 298110 6406 ms (max 1.8) label 57706 1286 ms (max 19.6) fill 1464 12 ms (max 0.0) end 247 1 ms (max 0.0) flush 3894 0 ms (max 0.0); waited behind (holder/site ms): q0x3d/dispatch 216 presenter/present 40 q0x3d/flush 7 q0x3d/label 5 q0x3d/hook 1; lock() 698125 / try 0 / try failed 0; locked GPU waits 122 / 1133 ms
[lock] queue 0x3d waited 23 ms for the GPU mutex in 2764 of 6368 acquisitions (10 s); by site (acquisitions/waits, waited): other 748/0 0 ms dispatch 976/819 5 ms hook 488/138 1 ms label 2928/748 7 ms flush 1220/1058 11 ms end 8/1 0 ms; held 245 ms in total, by site (holds, held, max): dispatch 976 219 ms (max 4.8) label 3217 13 ms (max 0.2) flush 1221 7 ms (max 0.1) hook 488 5 ms (max 0.0) try 1600 1 ms (max 0.0) end 8 0 ms (max 0.0); waited behind (holder/site ms): untagged/draw 46; lock() 6368 / try 30501 / try failed 28611; locked GPU waits 292 / 164 ms
[flip] (cumulative): 3521 presents from the resident image (0 refreshed first), through guest memory: 0 not pending, 0 unsuitable; 11 GPU frame dumps

@EagleBearerYS

EagleBearerYS commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor Author

@fma-rc thanks for the profiled rounds: every present came from the resident image in both arms, so this PR's path didn't run in Astro's Playroom (PPSA01325) either.

@thuanquang

Copy link
Copy Markdown
Contributor

@EagleBearerYS here is a Windows run on AMD's proprietary driver, a third vendor next to the RTX 4090 and RADV runs: Radeon RX 6700 XT (gfx1031), AMD driver 32.0.21045.16010, Windows 11 26200, Ryzen 5 5600X, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON, ANYPS5_REQUIRE_VULKAN=1. It tests this head (fdc4da36d2) merged onto main e05ba6feeb; the merge was clean.

  • The new test can't run here either. agc_driver_present_lock_wait skips with skipped, the device has no VK_EXT_headless_surface for a presentation window. vulkaninfo confirms that AMD's Windows driver doesn't expose that extension: its surface extensions are only VK_KHR_surface and VK_KHR_win32_surface. So neither proprietary Windows driver can run it.
  • The selected tests pass, apart from known main failures. tools/select_tests.py selects 1464 of 2357 tests for this change. All pass except:
    • agc_host_import_cut_lifetime, agc_driver_depth_plane_partial_store and agc_driver_sample_order_grid_reference. They fail the same way on unmodified main (4017310678) on this GPU, 3 of 3 runs in each tree.
    • public_headers, which goes over its 30 s ctest timeout on this machine. Its audit passes when run directly (exit 0, "368 libraries, 12192 exports"). Because it timed out, its 64 compile batches didn't run in the selection; run on their own, all 64 pass.
  • The skips are expected. tools/check_skips.py --job windows accepts all 10 of them.

On the open question of a title whose presents go through guest memory, neither of our two titles is one. PPSA14363 (METAL GEAR SOLID Master Collection) reports through guest memory: 0 not pending, 0 unsuitable in its [flip] lines. PPSA05110 (Persona 5 Royal) sends at most one present that way out of about 1,300, and every other present comes from the resident image.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@thuanquang thank you, this helps on both open points. A third driver for the new test shows that neither proprietary Windows driver has VK_EXT_headless_surface, and the [flip] counts from Metal Gear Solid Master Collection and Persona 5 Royal add two more titles that present from the resident image. I've added your run and both titles to the description.

core/libs/prx/libSceAgcDriver/tests/execution/PresentLockWait.cpp: the
two AgcDriverPresentBuffer_nid_postfix calls pass nullptr for the overlay
plane parameter that boykopovar#10507 added, as main's other callers without a
plane do. The merge has no textual conflict; without this the test does
not build on main.

@jotitadev jotitadev left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: EagleBearerYS is right. This is the known Mesa 25.3.0–26.0.3 headless WSI bug (fixed in 26.0.4, see #10043), the test passes on CI's Mesa 25.2.8, and no lavapipe skip is needed. The rest is left as I first posted it.

Linux run of head 6afa8530c3 on lavapipe, a driver that has VK_EXT_headless_surface, so agc_driver_present_lock_wait doesn't skip on it. The test crashes there every time, and it crashes the same way without this PR's driver change.

Setup: Ubuntu 26.04, GCC 15.2, Release. Lavapipe from Mesa 26.0.3 (LLVM 21.1.8) and an RTX 3070 with NVIDIA 595.99.02, one at a time via VK_DRIVER_FILES. The head is merged --no-ff onto main 809c7fa2df with no conflicts. ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1, ctest -j1.

  • RTX 3070: skips 3/3 (the device has no VK_EXT_headless_surface for a presentation window), as on SleglJan's 3080.
  • Lavapipe: SEGFAULT in every run, about 1.2 s in, right after Surface capabilities drawable=64x64 … minImages=4:
    • this head, VVL 1.4.341: 6/6;
    • this head with APS5_NO_EARLY_DISPLAY_FLUSH=1: 3/3 with VVL 1.4.341, 2/2 with no layer;
    • main plus only PresentLockWait.cpp: 1/1;
    • with MESA_VK_WSI_DEBUG=sw or =linear: still crashes.
  • Where: the fault is a call through a null function pointer (rip=0). The return address is inside libvulkan_lvp.so, at a call site that fills a VkMemoryGetFdInfoKHR (sType 1000074002) just before the call. So it crashes in lavapipe's WSI while the presenter sets up the headless swapchain, before the timing part of the test runs. I have no Mesa debug symbols on this box, so I can't name the function. It may be a Mesa 26.0.3 bug; I haven't tried another Mesa version.
  • The rest of ^agc (minus dcc/flip/lds_atomics64) passes on both devices with the PR: 1133/1133 on the 3070, and 1132/1133 on lavapipe, where the one failure is this crash. agc_driver_flip, agc_driver_present_mode and agc_driver_presentation_pass pass on lavapipe. No AGC test on main calls AgcDriverAttachWindow, so this test is the first one that reaches the presenter with a real surface on lavapipe.

So this run can't give a red/green for the change. The issue is that once a Linux job runs this test on lavapipe, which is what the Linux CI uses, it would probably crash rather than skip. A skip on lavapipe (VK_DRIVER_ID_MESA_LLVMPIPE) next to the headless-surface check would avoid that, unless you want to chase the Mesa side.

GPUs tested: NVIDIA GeForce RTX 3070, 595.99.02, Ubuntu 26.04 (skips); Mesa 26.0.3 lavapipe, Ubuntu 26.04 (crashes)

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@jotitadev thank you for running it on both. The lavapipe crash is the Mesa bug from #10043's agc_driver_present_upscale (details), not this test or the PR.

  • The bug: from Mesa 25.3.0 to 26.0.3, a VK_EXT_headless_surface swapchain on lavapipe allocates its images as exportable dma-buf memory. It then calls GetMemoryFdKHR, which the WSI never loads for a software device. That's your rip=0 right after a VkMemoryGetFdInfoKHR fill.
  • Fixed upstream: Mesa 26.0.4 has "vulkan/wsi/headless: properly use CPU images for CPU devices". On 25.2.8 and 26.0.8 the same headless swapchain is created normally.
  • On GitHub's Linux runner, the test runs and passes. It has Mesa 25.2.8, where agc_driver_present_lock_wait doesn't skip; it passed all 10 runs at 6afa853 (run).
    • So a lavapipe skip would turn off the one driver where CI runs it, to work around a Mesa bug that's already fixed. I'd rather leave it.
    • A runner image moving to Mesa 25.3.0–26.0.3 would hit it in agc_driver_present_upscale as well.

@jotitadev

Copy link
Copy Markdown
Contributor

@EagleBearerYS thanks, you're right: it's the Mesa 25.3.0–26.0.3 headless WSI bug, and the skip isn't needed. I've added a note at the top of my review.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Nov 4, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs needs rebase Conflicts with main, rebase needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants